﻿using System;
using System.Collections.Generic;
using System.Configuration;
using System.Net;
using PrearrangedChaos.AgileZen.Service.Model;
using Ramone;

namespace PrearrangedChaos.AgileZen.Service.Rest
{
    internal class ZenClient : IDisposable
    {
        private static Configuration _config;
        private ISession Session { get; set; }

        static ZenClient()
        {
            try
            {
                _config = ConfigurationManager.GetSection("prearrangedChaos/zenRestConfiguration") as Configuration;
            }
            catch (Exception)
            {
                _config = new Configuration();
            }

        }

        public ZenClient(string apiKey)
        {
            Session = RamoneConfiguration.NewSession(new Uri(_config.BaseAddress));

            Session.DefaultRequestMediaType = MediaType.ApplicationXml;
            Session.DefaultResponseMediaType = MediaType.ApplicationXml;
            Session.RequestInterceptors.Add(new ZenApiKeyRequestInterceptor(
                apiKey, _config.ConnectionTimeout, _config.ReadWriteTimeout));
        }

        public IEnumerable<TResponse> GetCollection<TResponse, TModel>(RestArguments arguments) 
            where TResponse : class, IPagingResponse<TModel>
        {
            int numberOfPages = 1;

            ZenRestRequestAttribute zenRestRequestAttribute = GetZenRestRequestAttribute<TResponse>();

            string relativeUrl = string.Format(
                "{0}?pageSize={{PageSize}}&page={{CurrentPage}}&with={{Enrichments}}&where={{Filter}}", 
                zenRestRequestAttribute.RelativeGetUrl);

            while (arguments.CurrentPage <= numberOfPages)
            {
                Request req = Session.Bind(relativeUrl, arguments);

                Response<TResponse> response = req.Get<TResponse>();

                ValidateResponseCode(response.StatusCode, req.Url);

                numberOfPages = response.Body.TotalPages;
                arguments.CurrentPage++;

                yield return response.Body;
            }
        }

        public TResponse Get<TResponse>(object arguments)
            where TResponse : class, IModel
        {
            ZenRestRequestAttribute zenRestRequestAttribute = GetZenRestRequestAttribute<TResponse>();

            Request req = Session.Bind(
                string.Format("{0}?with={{Enrichments}}", zenRestRequestAttribute.RelativeGetUrl), 
                arguments);

            Response<TResponse> response = req.Get<TResponse>();

            ValidateResponseCode(response.StatusCode, req.Url);

            return response.Body;
        }

        private ZenRestRequestAttribute GetZenRestRequestAttribute<TResponse>()
        {
            object[] zenRestRequestAttributes = typeof (TResponse).GetCustomAttributes(typeof (ZenRestRequestAttribute), false);
            if (zenRestRequestAttributes == null ||zenRestRequestAttributes.Length != 1)
                throw new ApplicationException("Response type is missing ZenRestRequestAttribute");

            return zenRestRequestAttributes[0] as ZenRestRequestAttribute;
        }

        private void ValidateResponseCode(HttpStatusCode statusCode, Uri url)
        {
            if (statusCode == HttpStatusCode.Forbidden)
                throw new AuthorizationException("Access to resource was denied", url);

            if (statusCode == HttpStatusCode.NotFound)
                throw new ResourceNotFoundException("Resource could not be found", url);

            if (statusCode == HttpStatusCode.BadRequest)
                throw new RequestInvalidException("Request was invalid", url);

            if (statusCode != HttpStatusCode.OK)
                throw new UnexpectedServerException("Server returned 500 error", url); 
        }

        public TResponse Create<TResponse>(IModel resource, object arguments) where TResponse : class, IModel
        {
            ZenRestRequestAttribute zenRestRequestAttribute = GetZenRestRequestAttribute<TResponse>();

            Request req = Session.Bind(zenRestRequestAttribute.RelativeCreateUrl, (object)arguments);

            Response<TResponse> response = req.Post<TResponse>(resource);

            ValidateResponseCode(response.StatusCode, req.Url);

            return response.Body;
        }

        public TModel Update<TModel, TModelUpdater>(TModelUpdater resource, object arguments)
            where TModelUpdater : IModelUpdater
            where TModel : class, IModel

        {
            ZenRestRequestAttribute zenRestRequestAttribute = GetZenRestRequestAttribute<TModel>();

            Request req = Session.Bind(zenRestRequestAttribute.RelativeGetUrl, (object)arguments);

            Response<TModel> response = req.Put<TModel>(resource);

            ValidateResponseCode(response.StatusCode, req.Url);

            return response.Body;
        }

        public void Add<TCollection, TModel>(TModel resource, object arguments) 
            where TModel : class, IModel
            where TCollection : IPagingResponse<TModel>
        {
            ZenRestRequestAttribute zenRestRequestAttribute = GetZenRestRequestAttribute<TCollection>();

            Request req = Session.Bind(zenRestRequestAttribute.RelativeGetUrl, (object)arguments);

            Response<TModel> response = req.Post<TModel>(resource);

            ValidateResponseCode(response.StatusCode, req.Url);
        }

        public void Delete<TModel>(object arguments)
            where TModel : class, IModel
        {
            ZenRestRequestAttribute zenRestRequestAttribute = GetZenRestRequestAttribute<TModel>();

            Request req = Session.Bind(zenRestRequestAttribute.RelativeGetUrl, (object)arguments);

            Response response = req.Delete();

            ValidateResponseCode(response.StatusCode, req.Url);
        }

        public void Remove<TCollection>(int resourceId, object arguments)
        {
            ZenRestRequestAttribute zenRestRequestAttribute = GetZenRestRequestAttribute<TCollection>();

            Request req = Session.Bind(zenRestRequestAttribute.RelativeGetUrl, arguments);

            Response response = req.Delete();

            ValidateResponseCode(response.StatusCode, req.Url);
        }

        public void Dispose()
        {
        }
    }
}
